Una cámara no “ve” continuo: toma muchas fotos por segundo.
Cada foto se llama frame (cuadro).
Un video es una secuencia de frames.
Ejemplo: 30 fps = 30 frames por segundo.
Idea clave:Primero capturamos una imagen. Luego la analizamos.
1B) Adquisición: ¿qué puede salir mal?
Luz baja → frame oscuro.
Movimiento → frame borroso.
Resolución baja → menos detalles.
Muchos frames/segundo → más “suave”, pero cuesta más procesar.
Regla simple: mejor iluminación + cámara estable = mejor punto de partida.
2A) Preprocesamiento: “arreglar” el frame para que sea más fácil
Es como limpiar unas gafas antes de leer.
Objetivo: que lo importante se vea más claro.
Cosas típicas:
Normalización: poner valores en un rango “parecido”.
Contraste: separar mejor claro/oscuro.
Ruido: quitar “granito” o puntos raros.
2B) Preprocesamiento: ejemplos sencillos
Normalizar: hacer que la imagen quede entre 0 y 1.
Aumentar contraste: que la cara se diferencie del fondo.
Suavizar ruido: un “filtro” que promedia vecinos.
Ojo: si suavizas demasiado, puedes borrar detalles (como la boca).
2B) Preprocesamiento: ejemplos sencillos
3A) Detección: localizar objetos (por ejemplo, un rostro)
“Detectar” = responder: ¿dónde está el objeto?
Resultado típico:
Un rectángulo (caja) alrededor del rostro.
Un nivel de confianza (qué tan seguro está el sistema).
Ejemplo: “Encontré 1 cara en (x, y, ancho, alto) con 0.92 de confianza”.
3B) Detección: cómo pensarla (sin entrar en cosas difíciles)
La computadora busca patrones que se parezcan a “rostro”.
Puede usar:
Métodos clásicos (formas, bordes).
Métodos modernos (modelos entrenados con muchas fotos).
Idea clave:Detectar no es “entender”, es “ubicar”.
3B) Detección: cómo pensarla (sin entrar en cosas difíciles)
4A) Análisis local: sub-detección dentro de una ROI
ROI = Región de Interés (una parte del frame).
Si ya encontraste el rostro, puedes mirar solo una parte:
La boca para detectar sonrisa.
Ventaja: menos área → más rápido y más simple.
4B) Análisis local: ejemplo “sonrisa en la boca”
Detectas el rostro (caja grande).
Recortas la ROI de la boca (caja pequeña).
Ejecutas un “mini detector” de sonrisa.
Sale un número: smile_score (0 a 1).
Idea clave: primero grande (rostro), luego pequeño (boca).
5A) Decisión: regla simple (hay detección vs no)
Aquí el sistema decide una salida final.
Ejemplo de regla muy simple:
Si confianza del rostro ≥ 0.6 → “hay rostro”.
Si no → “no hay rostro”.
Esto es una regla, no magia. A veces se equivoca.
5B) Decisión: ejemplo con “rostro + sonrisa”
Reglas de ejemplo:
Si rostro ≥ 0.6 y sonrisa ≥ 0.7 → “Sonrisa detectada”
Si rostro ≥ 0.6 y sonrisa < 0.7 → “Rostro sin sonrisa”
Si rostro < 0.6 → “Sin rostro”
Palabras útiles: - Falso positivo = detecta algo que no era. - Falso negativo = no detecta algo que sí estaba.
6A) Visualización: overlays (dibujos encima del frame)
Overlays = “capas” dibujadas sobre la imagen:
Rectángulos alrededor de objetos.
Puntos (ojos, nariz).
Texto (“Rostro: 0.92”, “Sonrisa: sí”).
Sirve para que un humano entienda rápido qué vio el sistema.
6B) Visualización: mini ejemplo (pseudo-código)
# frame: imagen original# face_box: (x, y, w, h)# score_face: 0.92draw_rectangle(frame, face_box)draw_text(frame, f"Rostro {score_face:.2f}", position=(x, y-10))# si también hay sonrisa:# draw_text(frame, "Sonrisa: SI", position=(x, y+h+20))show(frame)
Tiempo real: ¿por qué importa?
En tiempo real importan dos métricas:
Latencia: cuánto tarda en procesar un frame
FPS: cuántos frames por segundo se sostienen
La cascada Haar es popular porque es rápida en CPU, aunque no sea SOTA.
Imagen digital: la idea mínima
Una imagen es una matriz:
2D: (alto, ancho) si es escala de grises
3D: (alto, ancho, canales) si es color
En OpenCV, un frame de webcam suele ser uint8 en rango 0–255 por canal.
Espacios de color: BGR vs Gray
OpenCV usa BGR (no RGB) por defecto
El detector en cascada opera sobre una banda (grises)
Por eso aparece:
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
Conversión a grises: ¿qué se “pierde”?
Se reduce información cromática
Se conserva principalmente estructura: bordes, sombras, texturas
Para Haar/Viola–Jones, eso es suficiente:
busca patrones de contraste (claro/oscuro)
Preprocesamiento: ¿por qué tocar el contraste?
Problema: en webcam real hay - cambios de iluminación - sombras parciales - auto-exposure
Solución simple del script: - gray = cv2.equalizeHist(gray)
Histograma de intensidades (recordatorio)
El histograma cuenta cuántos píxeles hay en cada nivel de gris
Si la imagen está “oscura”, el histograma se concentra a la izquierda
Si está “lavada”, se concentra a la derecha o en un rango estrecho
La ecualización intenta “expandir” ese rango.
Ecualización de histograma: intuición
Transforma la imagen para que el histograma sea más uniforme
Efecto:
aumenta contraste global
puede mejorar detectabilidad de rasgos
también puede amplificar ruido en zonas oscuras
En el script se usa como robustez general.
¿Qué alternativa moderna usarías?
Para discusión: - CLAHE (ecualización adaptativa con límite de contraste) - Normalización por iluminación / homomorphic filtering - Modelos CNN con entrenamiento robusto
Pero aquí el objetivo es entender una cadena clásica, clara y rápida.
Detección: ¿qué es “detectar” aquí?
Entrada: imagen (grises)
Salida: lista de rectángulos:
cada rectángulo es un bounding box: (x, y, w, h)
En el código: - faces = face_cascade.detectMultiScale(...)
Viola–Jones / Haar Cascades (visión general)
Tres ideas clave:
Características Haar: patrones rectangulares de contraste
Imagen integral: cálculo ultrarrápido de sumas en rectángulos
Cascada: etapas de clasificación que rechazan rápido la mayoría de ventanas
Por eso funciona bien en tiempo real en CPU.
Características Haar (intuición)
No son bordes “bonitos”; son tests tipo:
“¿esta región es más clara que esta otra?”
Un rostro tiene regularidades:
zona ojos más oscura que mejillas
puente nasal, etc.
Una sonrisa también genera contrastes (boca/dientes/labios).
Imagen integral (por qué acelera)
Permite obtener la suma de intensidades de cualquier rectángulo con pocas operaciones
Esto hace viable evaluar miles de ventanas por frame
No lo implementas tú: OpenCV lo hace internamente.
Cascada: la estrategia
Etapas tempranas: baratas y estrictas → rechazan rápido
Etapas tardías: más costosas → solo para candidatos
Resultado: - menor carga computacional - pero depende mucho del dataset de entrenamiento del XML
Ventana deslizante + escalas
El detector “mueve” una ventana por la imagen
Repite el proceso en múltiples escalas (para detectar objetos grandes/pequeños)
Esto es postprocesamiento mínimo y una decisión binaria simple.
Decisión binaria: ¿qué significa realmente?
“Sonrisa detectada” ≠ emoción real
Solo significa:
“se encontraron patrones compatibles con el clasificador”
Importante para evitar sobreinterpretación, sobre todo en contextos biomédicos.
Bucles de adquisición: control de flujo
cap = cv2.VideoCapture(0) abre la webcam
ret, frame = cap.read() obtiene un frame
cv2.imshow(...) muestra la salida
cv2.waitKey(1) permite refresco y captura tecla
if ... == ord("q") sale del loop
Robustez: validaciones del script
Verifica que los XML cargaron:
if self.face_cascade.empty() or self.smile_cascade.empty(): raise ... :content
Verifica acceso a webcam:
if not cap.isOpened(): ... return
Concepto de ingeniería: fallar temprano con errores claros.
Rendimiento: ¿dónde se gasta el tiempo?
Mayor costo: detectMultiScale
porque evalúa muchas ventanas por frame
Reducir costo:
trabajar con ROI
subir scaleFactor (menos escalas)
subir minSize
bajar resolución del frame antes de detectar
Mini-experimento mental (5 min)
Si hay demasiados falsos positivos de sonrisa: - sube minNeighbors (ya está alto: 32) - sube minSize - reduce ecualización o usa CLAHE - restringe ROI a zona inferior del rostro (boca) en vez de todo el rostro
Mini-experimento mental (5 min)
Si no detecta sonrisas en gente lejos: - baja minSize (p.ej. 15x15) - baja scaleFactor (p.ej. 1.3) - baja minNeighbors (p.ej. 15–25) - aumenta iluminación o mejora exposición
Trade-off inevitable: sensibilidad vs falsos positivos.
Errores típicos (y por qué pasan)
Pose (cara girada): cascada frontal falla
Oclusión (mano, mascarilla): pierde rasgos
Luz dura (contraluz): cambia contrastes → se rompe Haar
Resolución baja: rasgos poco definidos
Expresiones sutiles: patrón no coincide con entrenamiento
¿Qué “conceptos” debes dominar para entender el script?
Checklist:
Imagen como matriz, tipo de dato uint8
Espacios de color (BGR) y conversión a grises
Histograma y ecualización
Detección de objetos vs clasificación
Bounding boxes y coordenadas
ROI (recorte) y jerarquía de detección
Viola–Jones: Haar features, imagen integral, cascada
Parámetros detectMultiScale y trade-offs
Visualización en tiempo real (imshow, waitKey)
Todo aparece en el flujo del código.
Actividad sugerida (para estudiantes)
Ejecutar el script tal cual
Cambiar un parámetro a la vez (registrar):
scaleFactor (rostro)
minNeighbors (rostro)
minNeighbors (sonrisa)
minSize (sonrisa)
Documentar:
FPS aproximado (sensación)
falsos positivos
falsos negativos
Extensión 1: mejorar contraste sin amplificar tanto ruido
Sustituir equalizeHist por CLAHE
Comparar resultados con iluminación variable
(Útil en escenarios biomédicos con condiciones de luz no controladas)
Extensión 2: segmentar “zona boca” dentro del rostro
Dividir el rostro en regiones:
parte inferior ~ 40%–55% del alto
Detectar sonrisa solo allí:
menos falsos positivos
mejor rendimiento
Extensión 3: migrar a métodos modernos (discusión)